/*
 * Copyright (c) 2000-2003 Fabrice Bellard
 *
 * This file is part of FFmpeg.
 *
 * FFmpeg is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * FFmpeg is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with FFmpeg; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

/**
 * @file
 * multimedia converter based on the FFmpeg libraries
 */

#include "config.h"

#include <errno.h>
#include <limits.h>
#include <stdatomic.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#if HAVE_IO_H
#include <io.h>
#endif
#if HAVE_UNISTD_H
#include <unistd.h>
#endif

#if HAVE_SYS_RESOURCE_H
#include <sys/time.h>
#include <sys/types.h>
#include <sys/resource.h>
#elif HAVE_GETPROCESSTIMES
#include <windows.h>
#endif
#if HAVE_GETPROCESSMEMORYINFO
#include <windows.h>
#include <psapi.h>
#endif

#if HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif

#include "libavutil/avassert.h"
#include "libavutil/avstring.h"
#include "libavutil/bprint.h"
#include "libavutil/channel_layout.h"
#include "libavutil/dict.h"
#include "libavutil/display.h"
#include "libavutil/fifo.h"
#include "libavutil/hwcontext.h"
#include "libavutil/imgutils.h"
#include "libavutil/intreadwrite.h"
#include "libavutil/libm.h"
#include "libavutil/mathematics.h"
#include "libavutil/opt.h"
#include "libavutil/parseutils.h"
#include "libavutil/pixdesc.h"
#include "libavutil/samplefmt.h"
#include "libavutil/thread.h"
#include "libavutil/threadmessage.h"
#include "libavutil/time.h"
#include "libavutil/timestamp.h"

#include "libavcodec/version.h"

#include "libavformat/avformat.h"

#include "libavdevice/avdevice.h"

#include "libswresample/swresample.h"

#include "cmdutils.h"
#include "ffmpeg.h"
#include "sync_queue.h"

const char program_name[] = "ffmpeg";
const int program_birth_year = 2000;

static BenchmarkTimeStamps get_benchmark_time_stamps(void);
static int64_t getmaxrss(void);

/* sub2video hack:
   Convert subtitles to video with alpha to insert them in filter graphs.
   This is a temporary solution until libavfilter gets real subtitles support.
 */

static void sub2video_heartbeat(InputFile *infile, int64_t pts, AVRational tb)
{
    /* When a frame is read from a file, examine all sub2video streams in
       the same file and send the sub2video frame again. Otherwise, decoded
       video frames could be accumulating in the filter graph while a filter
       (possibly overlay) is desperately waiting for a subtitle frame. */
    for (int i = 0; i < infile->nb_streams; i++) {
        InputStream *ist = infile->streams[i];

        if (ist->dec_ctx->codec_type != AVMEDIA_TYPE_SUBTITLE)
            continue;

        for (int j = 0; j < ist->nb_filters; j++)
            ifilter_sub2video_heartbeat(ist->filters[j], pts, tb);
    }
}

/* end of sub2video hack */

static int decode_interrupt_cb(void *ctx)
{
	FFMPEGContext *pffmpeg_ctt=ctx;
    return pffmpeg_ctt->received_stop;
}

static void ffmpeg_cleanup(int ret,FFMPEGContext *pffmpeg_ctt)
{
    int i;

    if (do_benchmark) {
        int maxrss = getmaxrss() / 1024;
        av_log(NULL, AV_LOG_INFO, "bench: maxrss=%ikB\n", maxrss);
    }

    for (i = 0; i < pffmpeg_ctt->nb_filtergraphs; i++)
        fg_free(&pffmpeg_ctt->filtergraphs[i]);
    av_freep(&pffmpeg_ctt->filtergraphs);

    for (i = 0; i < pffmpeg_ctt->nb_output_files; i++)
        of_free(&pffmpeg_ctt->output_files[i]);

    for (i = 0; i < pffmpeg_ctt->nb_input_files; i++)
        ifile_close(&pffmpeg_ctt->input_files[i]);

    if (pffmpeg_ctt->vstats_file) {
        if (fclose(pffmpeg_ctt->vstats_file))
            av_log(NULL, AV_LOG_ERROR,
                   "Error closing vstats file, loss of information possible: %s\n",
                   av_err2str(AVERROR(errno)));
    }
    av_freep(&pffmpeg_ctt->vstats_filename);
    of_enc_stats_close(pffmpeg_ctt);

    hw_device_free_all(pffmpeg_ctt);

    av_freep(&pffmpeg_ctt->filter_nbthreads);

    av_freep(&pffmpeg_ctt->input_files);
    av_freep(&pffmpeg_ctt->output_files);

    uninit_opts(&pffmpeg_ctt->cmdutils_ctt);

    avformat_network_deinit();

    if (pffmpeg_ctt->received_stop) {
        av_log(NULL, AV_LOG_INFO, "Exiting normally, received signal %d.\n",
               (int) pffmpeg_ctt->received_stop);
    } else if (ret && atomic_load(&pffmpeg_ctt->transcode_init_done)) {
        av_log(NULL, AV_LOG_INFO, "Conversion failed!\n");
    }
    pffmpeg_ctt->ffmpeg_exited = 1;
}

OutputStream *ost_iter(OutputStream *prev,FFMPEGContext *pffmpeg_ctt)
{
    int of_idx  = prev ? prev->file_index : 0;
    int ost_idx = prev ? prev->index + 1  : 0;

    for (; of_idx < pffmpeg_ctt->nb_output_files; of_idx++) {
        OutputFile *of = pffmpeg_ctt->output_files[of_idx];
        if (ost_idx < of->nb_streams)
            return of->streams[ost_idx];

        ost_idx = 0;
    }

    return NULL;
}

InputStream *ist_iter(InputStream *prev,FFMPEGContext *pffmpeg_ctt)
{
    int if_idx  = prev ? prev->file_index : 0;
    int ist_idx = prev ? prev->index + 1  : 0;

    for (; if_idx < pffmpeg_ctt->nb_input_files; if_idx++) {
        InputFile *f = pffmpeg_ctt->input_files[if_idx];
        if (ist_idx < f->nb_streams)
            return f->streams[ist_idx];

        ist_idx = 0;
    }

    return NULL;
}

FrameData *frame_data(AVFrame *frame)
{
    if (!frame->opaque_ref) {
        FrameData *fd;

        frame->opaque_ref = av_buffer_allocz(sizeof(*fd));
        if (!frame->opaque_ref)
            return NULL;
        fd = (FrameData*)frame->opaque_ref->data;

        fd->dec.frame_num = UINT64_MAX;
        fd->dec.pts       = AV_NOPTS_VALUE;
    }

    return (FrameData*)frame->opaque_ref->data;
}

void remove_avoptions(AVDictionary **a, AVDictionary *b)
{
    const AVDictionaryEntry *t = NULL;

    while ((t = av_dict_iterate(b, t))) {
        av_dict_set(a, t->key, NULL, AV_DICT_MATCH_CASE);
    }
}

int check_avoptions(AVDictionary *m)
{
    const AVDictionaryEntry *t;
    if ((t = av_dict_get(m, "", NULL, AV_DICT_IGNORE_SUFFIX))) {
        av_log(NULL, AV_LOG_FATAL, "Option %s not found.\n", t->key);
        return AVERROR_OPTION_NOT_FOUND;
    }

    return 0;
}

void update_benchmark(FFMPEGContext *pffmpeg_ctt,const char *fmt, ...)
{
    if (do_benchmark_all) {
        BenchmarkTimeStamps t = get_benchmark_time_stamps();
        va_list va;
        char buf[1024];

        if (fmt) {
            va_start(va, fmt);
            vsnprintf(buf, sizeof(buf), fmt, va);
            va_end(va);
            av_log(NULL, AV_LOG_INFO,
                   "bench: %8" PRIu64 " user %8" PRIu64 " sys %8" PRIu64 " real %s \n",
                   t.user_usec - pffmpeg_ctt->current_time.user_usec,
                   t.sys_usec - pffmpeg_ctt->current_time.sys_usec,
                   t.real_usec - pffmpeg_ctt->current_time.real_usec, buf);
        }
        pffmpeg_ctt->current_time = t;
    }
}

void close_output_stream(OutputStream *ost,FFMPEGContext *pffmpeg_ctt)
{
    OutputFile *of = pffmpeg_ctt->output_files[ost->file_index];
    ost->finished |= ENCODER_FINISHED;

    if (ost->sq_idx_encode >= 0)
        sq_send(of->sq_encode, ost->sq_idx_encode, SQFRAME(NULL));
}

static void print_report(int is_last_report, int64_t timer_start, int64_t cur_time,FFMPEGContext *pffmpeg_ctt)
{
    AVBPrint buf, buf_script;
    int64_t total_size = of_filesize(pffmpeg_ctt->output_files[0]);
    int vid;
    double bitrate;
    double speed;
    int64_t pts = AV_NOPTS_VALUE;
    static int64_t last_time = -1;
    static int first_report = 1;
    uint64_t nb_frames_dup = 0, nb_frames_drop = 0;
    int mins, secs, us;
    int64_t hours;
    const char *hours_sign;
    int ret;
    float t;

    if (!print_stats && !is_last_report && !pffmpeg_ctt->progress_avio)
        return;

    if (!is_last_report) {
        if (last_time == -1) {
            last_time = cur_time;
        }
        if (((cur_time - last_time) < pffmpeg_ctt->stats_period && !first_report) ||
            (first_report && pffmpeg_ctt->nb_output_dumped < pffmpeg_ctt->nb_output_files))
            return;
        last_time = cur_time;
    }

    t = (cur_time-timer_start) / 1000000.0;

    vid = 0;
    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
    av_bprint_init(&buf_script, 0, AV_BPRINT_SIZE_AUTOMATIC);
    for (OutputStream *ost = ost_iter(NULL,pffmpeg_ctt); ost; ost = ost_iter(ost,pffmpeg_ctt)) {
        const float q = ost->enc ? ost->quality / (float) FF_QP2LAMBDA : -1;

        if (vid && ost->type == AVMEDIA_TYPE_VIDEO) {
            av_bprintf(&buf, "q=%2.1f ", q);
            av_bprintf(&buf_script, "stream_%d_%d_q=%.1f\n",
                       ost->file_index, ost->index, q);
        }
        if (!vid && ost->type == AVMEDIA_TYPE_VIDEO && ost->filter) {
            float fps;
            uint64_t frame_number = atomic_load(&ost->packets_written);

            fps = t > 1 ? frame_number / t : 0;
            av_bprintf(&buf, "frame=%5"PRId64" fps=%3.*f q=%3.1f ",
                     frame_number, fps < 9.95, fps, q);
            av_bprintf(&buf_script, "frame=%"PRId64"\n", frame_number);
            av_bprintf(&buf_script, "fps=%.2f\n", fps);
            av_bprintf(&buf_script, "stream_%d_%d_q=%.1f\n",
                       ost->file_index, ost->index, q);
            if (is_last_report)
                av_bprintf(&buf, "L");

            nb_frames_dup  = ost->filter->nb_frames_dup;
            nb_frames_drop = ost->filter->nb_frames_drop;

            vid = 1;
        }
        /* compute min output value */
        if (ost->last_mux_dts != AV_NOPTS_VALUE) {
            if (pts == AV_NOPTS_VALUE || ost->last_mux_dts > pts)
                pts = ost->last_mux_dts;
            if (copy_ts) {
                if (pffmpeg_ctt->copy_ts_first_pts == AV_NOPTS_VALUE && pts > 1)
                    pffmpeg_ctt->copy_ts_first_pts = pts;
                if (pffmpeg_ctt->copy_ts_first_pts != AV_NOPTS_VALUE)
                    pts -= pffmpeg_ctt->copy_ts_first_pts;
            }
        }
    }

    us    = FFABS64U(pts) % AV_TIME_BASE;
    secs  = FFABS64U(pts) / AV_TIME_BASE % 60;
    mins  = FFABS64U(pts) / AV_TIME_BASE / 60 % 60;
    hours = FFABS64U(pts) / AV_TIME_BASE / 3600;
    hours_sign = (pts < 0) ? "-" : "";

    bitrate = pts != AV_NOPTS_VALUE && pts && total_size >= 0 ? total_size * 8 / (pts / 1000.0) : -1;
    speed   = pts != AV_NOPTS_VALUE && t != 0.0 ? (double)pts / AV_TIME_BASE / t : -1;

    if (total_size < 0) av_bprintf(&buf, "size=N/A time=");
    else                av_bprintf(&buf, "size=%8.0fkB time=", total_size / 1024.0);
    if (pts == AV_NOPTS_VALUE) {
        av_bprintf(&buf, "N/A ");
    } else {
        av_bprintf(&buf, "%s%02"PRId64":%02d:%02d.%02d ",
                   hours_sign, hours, mins, secs, (100 * us) / AV_TIME_BASE);
    }

    if (bitrate < 0) {
        av_bprintf(&buf, "bitrate=N/A");
        av_bprintf(&buf_script, "bitrate=N/A\n");
    }else{
        av_bprintf(&buf, "bitrate=%6.1fkbits/s", bitrate);
        av_bprintf(&buf_script, "bitrate=%6.1fkbits/s\n", bitrate);
    }

    if (total_size < 0) av_bprintf(&buf_script, "total_size=N/A\n");
    else                av_bprintf(&buf_script, "total_size=%"PRId64"\n", total_size);
    if (pts == AV_NOPTS_VALUE) {
        av_bprintf(&buf_script, "out_time_us=N/A\n");
        av_bprintf(&buf_script, "out_time_ms=N/A\n");
        av_bprintf(&buf_script, "out_time=N/A\n");
    } else {
        av_bprintf(&buf_script, "out_time_us=%"PRId64"\n", pts);
        av_bprintf(&buf_script, "out_time_ms=%"PRId64"\n", pts);
        av_bprintf(&buf_script, "out_time=%s%02"PRId64":%02d:%02d.%06d\n",
                   hours_sign, hours, mins, secs, us);
    }

    if (nb_frames_dup || nb_frames_drop)
        av_bprintf(&buf, " dup=%"PRId64" drop=%"PRId64, nb_frames_dup, nb_frames_drop);
    av_bprintf(&buf_script, "dup_frames=%"PRId64"\n", nb_frames_dup);
    av_bprintf(&buf_script, "drop_frames=%"PRId64"\n", nb_frames_drop);

    if (speed < 0) {
        av_bprintf(&buf, " speed=N/A");
        av_bprintf(&buf_script, "speed=N/A\n");
    } else {
        av_bprintf(&buf, " speed=%4.3gx", speed);
        av_bprintf(&buf_script, "speed=%4.3gx\n", speed);
    }

    if (print_stats || is_last_report) {
        const char end = is_last_report ? '\n' : '\r';
        if (print_stats==1 && AV_LOG_INFO > av_log_get_level()) {
            fprintf(stderr, "%s    %c", buf.str, end);
        } else
            av_log(NULL, AV_LOG_INFO, "%s    %c", buf.str, end);

        fflush(stderr);
    }
    av_bprint_finalize(&buf, NULL);

    if (pffmpeg_ctt->progress_avio) {
        av_bprintf(&buf_script, "progress=%s\n",
                   is_last_report ? "end" : "continue");
        avio_write(pffmpeg_ctt->progress_avio, buf_script.str,
                   FFMIN(buf_script.len, buf_script.size - 1));
        avio_flush(pffmpeg_ctt->progress_avio);
        av_bprint_finalize(&buf_script, NULL);
        if (is_last_report) {
            if ((ret = avio_closep(&pffmpeg_ctt->progress_avio)) < 0)
                av_log(NULL, AV_LOG_ERROR,
                       "Error closing progress log, loss of information possible: %s\n", av_err2str(ret));
        }
    }

    first_report = 0;
}

int copy_av_subtitle(AVSubtitle *dst, const AVSubtitle *src)
{
    int ret = AVERROR_BUG;
    AVSubtitle tmp = {
        .format = src->format,
        .start_display_time = src->start_display_time,
        .end_display_time = src->end_display_time,
        .num_rects = 0,
        .rects = NULL,
        .pts = src->pts
    };

    if (!src->num_rects)
        goto success;

    if (!(tmp.rects = av_calloc(src->num_rects, sizeof(*tmp.rects))))
        return AVERROR(ENOMEM);

    for (int i = 0; i < src->num_rects; i++) {
        AVSubtitleRect *src_rect = src->rects[i];
        AVSubtitleRect *dst_rect;

        if (!(dst_rect = tmp.rects[i] = av_mallocz(sizeof(*tmp.rects[0])))) {
            ret = AVERROR(ENOMEM);
            goto cleanup;
        }

        tmp.num_rects++;

        dst_rect->type      = src_rect->type;
        dst_rect->flags     = src_rect->flags;

        dst_rect->x         = src_rect->x;
        dst_rect->y         = src_rect->y;
        dst_rect->w         = src_rect->w;
        dst_rect->h         = src_rect->h;
        dst_rect->nb_colors = src_rect->nb_colors;

        if (src_rect->text)
            if (!(dst_rect->text = av_strdup(src_rect->text))) {
                ret = AVERROR(ENOMEM);
                goto cleanup;
            }

        if (src_rect->ass)
            if (!(dst_rect->ass = av_strdup(src_rect->ass))) {
                ret = AVERROR(ENOMEM);
                goto cleanup;
            }

        for (int j = 0; j < 4; j++) {
            // SUBTITLE_BITMAP images are special in the sense that they
            // are like PAL8 images. first pointer to data, second to
            // palette. This makes the size calculation match this.
            size_t buf_size = src_rect->type == SUBTITLE_BITMAP && j == 1 ?
                              AVPALETTE_SIZE :
                              src_rect->h * src_rect->linesize[j];

            if (!src_rect->data[j])
                continue;

            if (!(dst_rect->data[j] = av_memdup(src_rect->data[j], buf_size))) {
                ret = AVERROR(ENOMEM);
                goto cleanup;
            }
            dst_rect->linesize[j] = src_rect->linesize[j];
        }
    }

success:
    *dst = tmp;

    return 0;

cleanup:
    avsubtitle_free(&tmp);

    return ret;
}

static void subtitle_free(void *opaque, uint8_t *data)
{
    AVSubtitle *sub = (AVSubtitle*)data;
    avsubtitle_free(sub);
    av_free(sub);
}

int subtitle_wrap_frame(AVFrame *frame, AVSubtitle *subtitle, int copy)
{
    AVBufferRef *buf;
    AVSubtitle *sub;
    int ret;

    if (copy) {
        sub = av_mallocz(sizeof(*sub));
        ret = sub ? copy_av_subtitle(sub, subtitle) : AVERROR(ENOMEM);
        if (ret < 0) {
            av_freep(&sub);
            return ret;
        }
    } else {
        sub = av_memdup(subtitle, sizeof(*subtitle));
        if (!sub)
            return AVERROR(ENOMEM);
        memset(subtitle, 0, sizeof(*subtitle));
    }

    buf = av_buffer_create((uint8_t*)sub, sizeof(*sub),
                           subtitle_free, NULL, 0);
    if (!buf) {
        avsubtitle_free(sub);
        av_freep(&sub);
        return AVERROR(ENOMEM);
    }

    frame->buf[0] = buf;

    return 0;
}

int trigger_fix_sub_duration_heartbeat(OutputStream *ost, const AVPacket *pkt,FFMPEGContext *pffmpeg_ctt)
{
    OutputFile *of = pffmpeg_ctt->output_files[ost->file_index];
    int64_t signal_pts = av_rescale_q(pkt->pts, pkt->time_base,
                                      AV_TIME_BASE_Q);

    if (!ost->fix_sub_duration_heartbeat || !(pkt->flags & AV_PKT_FLAG_KEY))
        // we are only interested in heartbeats on streams configured, and
        // only on random access points.
        return 0;

    for (int i = 0; i < of->nb_streams; i++) {
        OutputStream *iter_ost = of->streams[i];
        InputStream  *ist      = iter_ost->ist;
        int ret = AVERROR_BUG;

        if (iter_ost == ost || !ist || !ist->decoding_needed ||
            ist->dec_ctx->codec_type != AVMEDIA_TYPE_SUBTITLE)
            // We wish to skip the stream that causes the heartbeat,
            // output streams without an input stream, streams not decoded
            // (as fix_sub_duration is only done for decoded subtitles) as
            // well as non-subtitle streams.
            continue;

        if ((ret = fix_sub_duration_heartbeat(ist, signal_pts,pffmpeg_ctt)) < 0)
            return ret;
    }

    return 0;
}

/* pkt = NULL means EOF (needed to flush decoder buffers) */
static int process_input_packet(InputStream *ist, const AVPacket *pkt, int no_eof,FFMPEGContext *pffmpeg_ctt)
{
    InputFile *f = pffmpeg_ctt->input_files[ist->file_index];
    int64_t dts_est = AV_NOPTS_VALUE;
    int ret = 0;
    int eof_reached = 0;

    if (ist->decoding_needed) {
        ret = dec_packet(ist, pkt, no_eof,pffmpeg_ctt);
        if (ret < 0 && ret != AVERROR_EOF)
            return ret;
    }
    if (ret == AVERROR_EOF || (!pkt && !ist->decoding_needed))
        eof_reached = 1;

    if (pkt && pkt->opaque_ref) {
        DemuxPktData *pd = (DemuxPktData*)pkt->opaque_ref->data;
        dts_est = pd->dts_est;
    }

    if (f->recording_time != INT64_MAX) {
        int64_t start_time = 0;
        if (copy_ts) {
            start_time += f->start_time != AV_NOPTS_VALUE ? f->start_time : 0;
            start_time += start_at_zero ? 0 : f->start_time_effective;
        }
        if (dts_est >= f->recording_time + start_time)
            pkt = NULL;
    }

    for (int oidx = 0; oidx < ist->nb_outputs; oidx++) {
        OutputStream *ost = ist->outputs[oidx];
        if (ost->enc || (!pkt && no_eof))
            continue;

        ret = of_streamcopy(ost, pkt, dts_est,pffmpeg_ctt);
        if (ret < 0)
            return ret;
    }

    return !eof_reached;
}

static void print_stream_maps(FFMPEGContext *pffmpeg_ctt)
{
    av_log(NULL, AV_LOG_INFO, "Stream mapping:\n");
    for (InputStream *ist = ist_iter(NULL,pffmpeg_ctt); ist; ist = ist_iter(ist,pffmpeg_ctt)) {
        for (int j = 0; j < ist->nb_filters; j++) {
            if (!filtergraph_is_simple(ist->filters[j]->graph)) {
                av_log(NULL, AV_LOG_INFO, "  Stream #%d:%d (%s) -> %s",
                       ist->file_index, ist->index, ist->dec ? ist->dec->name : "?",
                       ist->filters[j]->name);
                if (pffmpeg_ctt->nb_filtergraphs > 1)
                    av_log(NULL, AV_LOG_INFO, " (graph %d)", ist->filters[j]->graph->index);
                av_log(NULL, AV_LOG_INFO, "\n");
            }
        }
    }

    for (OutputStream *ost = ost_iter(NULL,pffmpeg_ctt); ost; ost = ost_iter(ost,pffmpeg_ctt)) {
        if (ost->attachment_filename) {
            /* an attached file */
            av_log(NULL, AV_LOG_INFO, "  File %s -> Stream #%d:%d\n",
                   ost->attachment_filename, ost->file_index, ost->index);
            continue;
        }

        if (ost->filter && !filtergraph_is_simple(ost->filter->graph)) {
            /* output from a complex graph */
            av_log(NULL, AV_LOG_INFO, "  %s", ost->filter->name);
            if (pffmpeg_ctt->nb_filtergraphs > 1)
                av_log(NULL, AV_LOG_INFO, " (graph %d)", ost->filter->graph->index);

            av_log(NULL, AV_LOG_INFO, " -> Stream #%d:%d (%s)\n", ost->file_index,
                   ost->index, ost->enc_ctx->codec->name);
            continue;
        }

        av_log(NULL, AV_LOG_INFO, "  Stream #%d:%d -> #%d:%d",
               ost->ist->file_index,
               ost->ist->index,
               ost->file_index,
               ost->index);
        if (ost->enc_ctx) {
            const AVCodec *in_codec    = ost->ist->dec;
            const AVCodec *out_codec   = ost->enc_ctx->codec;
            const char *decoder_name   = "?";
            const char *in_codec_name  = "?";
            const char *encoder_name   = "?";
            const char *out_codec_name = "?";
            const AVCodecDescriptor *desc;

            if (in_codec) {
                decoder_name  = in_codec->name;
                desc = avcodec_descriptor_get(in_codec->id);
                if (desc)
                    in_codec_name = desc->name;
                if (!strcmp(decoder_name, in_codec_name))
                    decoder_name = "native";
            }

            if (out_codec) {
                encoder_name   = out_codec->name;
                desc = avcodec_descriptor_get(out_codec->id);
                if (desc)
                    out_codec_name = desc->name;
                if (!strcmp(encoder_name, out_codec_name))
                    encoder_name = "native";
            }

            av_log(NULL, AV_LOG_INFO, " (%s (%s) -> %s (%s))",
                   in_codec_name, decoder_name,
                   out_codec_name, encoder_name);
        } else
            av_log(NULL, AV_LOG_INFO, " (copy)");
        av_log(NULL, AV_LOG_INFO, "\n");
    }
}

/**
 * Select the output stream to process.
 *
 * @retval 0 an output stream was selected
 * @retval AVERROR(EAGAIN) need to wait until more input is available
 * @retval AVERROR_EOF no more streams need output
 */
static int choose_output(OutputStream **post,FFMPEGContext *pffmpeg_ctt)
{
    int64_t opts_min = INT64_MAX;
    OutputStream *ost_min = NULL;

    for (OutputStream *ost = ost_iter(NULL,pffmpeg_ctt); ost; ost = ost_iter(ost,pffmpeg_ctt)) {
        int64_t opts;

        if (ost->filter && ost->filter->last_pts != AV_NOPTS_VALUE) {
            opts = ost->filter->last_pts;
        } else {
            opts = ost->last_mux_dts == AV_NOPTS_VALUE ?
                   INT64_MIN : ost->last_mux_dts;
        }

        if (!ost->initialized && !ost->finished) {
            ost_min = ost;
            break;
        }
        if (!ost->finished && opts < opts_min) {
            opts_min = opts;
            ost_min  = ost;
        }
    }
    if (!ost_min)
        return AVERROR_EOF;
    *post = ost_min;
    return ost_min->unavailable ? AVERROR(EAGAIN) : 0;
}
static void reset_eagain(FFMPEGContext *pffmpeg_ctt)
{
    int i;
    for (i = 0; i < pffmpeg_ctt->nb_input_files; i++)
        pffmpeg_ctt->input_files[i]->eagain = 0;
    for (OutputStream *ost = ost_iter(NULL,pffmpeg_ctt); ost; ost = ost_iter(ost,pffmpeg_ctt))
        ost->unavailable = 0;
}

static void decode_flush(InputFile *ifile,FFMPEGContext *pffmpeg_ctt)
{
    for (int i = 0; i < ifile->nb_streams; i++) {
        InputStream *ist = ifile->streams[i];

        if (ist->discard || !ist->decoding_needed)
            continue;

        dec_packet(ist, NULL, 1,pffmpeg_ctt);
    }
}

/*
 * Return
 * - 0 -- one packet was read and processed
 * - AVERROR(EAGAIN) -- no packets were available for selected file,
 *   this function should be called again
 * - AVERROR_EOF -- this function should not be called again
 */
static int process_input(int file_index,FFMPEGContext *pffmpeg_ctt)
{
    InputFile *ifile = pffmpeg_ctt->input_files[file_index];
    InputStream *ist;
    AVPacket *pkt;
    int ret, i;

    ret = ifile_get_packet(ifile, &pkt,pffmpeg_ctt);
	//获取输入码率
	if(pkt &&NULL!=pffmpeg_ctt->bitRate ){
		pffmpeg_ctt->bitRate(pffmpeg_ctt->bitRate_arg,pkt->size,1);
	}
    if (ret == AVERROR(EAGAIN)) {
        ifile->eagain = 1;
        return ret;
    }
    if (ret == 1) {
        /* the input file is looped: flush the decoders */
        decode_flush(ifile,pffmpeg_ctt);
        return AVERROR(EAGAIN);
    }
    if (ret < 0) {
        if (ret != AVERROR_EOF) {
            av_log(ifile, AV_LOG_ERROR,
                   "Error retrieving a packet from demuxer: %s\n", av_err2str(ret));
            if (exit_on_error)
                return ret;
        }

        for (i = 0; i < ifile->nb_streams; i++) {
            ist = ifile->streams[i];
            if (!ist->discard) {
                ret = process_input_packet(ist, NULL, 0,pffmpeg_ctt);
                if (ret>0)
                    return 0;
                else if (ret < 0)
                    return ret;
            }

            /* mark all outputs that don't go through lavfi as finished */
            for (int oidx = 0; oidx < ist->nb_outputs; oidx++) {
                OutputStream *ost = ist->outputs[oidx];
                OutputFile    *of = pffmpeg_ctt->output_files[ost->file_index];

                ret = of_output_packet(of, ost, NULL,pffmpeg_ctt);
                if (ret < 0)
                    return ret;
            }
        }

        ifile->eof_reached = 1;
        return AVERROR(EAGAIN);
    }

    reset_eagain(pffmpeg_ctt);

    ist = ifile->streams[pkt->stream_index];

    sub2video_heartbeat(ifile, pkt->pts, pkt->time_base);

    ret = process_input_packet(ist, pkt, 0,pffmpeg_ctt);

    av_packet_free(&pkt);

    return ret < 0 ? ret : 0;
}

/**
 * Run a single step of transcoding.
 *
 * @return  0 for success, <0 for error
 */
static int transcode_step(OutputStream *ost,FFMPEGContext *pffmpeg_ctt)
{
    InputStream  *ist = NULL;
    int ret;

    if (ost->filter) {
        if ((ret = fg_transcode_step(ost->filter->graph, &ist,pffmpeg_ctt)) < 0)
            return ret;
        if (!ist)
            return 0;
    } else {
        ist = ost->ist;
        av_assert0(ist);
    }

    ret = process_input(ist->file_index,pffmpeg_ctt);
    if (ret == AVERROR(EAGAIN)) {
        if (pffmpeg_ctt->input_files[ist->file_index]->eagain)
            ost->unavailable = 1;
        return 0;
    }

    if (ret < 0)
        return ret == AVERROR_EOF ? 0 : ret;

    // process_input() above might have caused output to become available
    // in multiple filtergraphs, so we process all of them
    for (int i = 0; i < pffmpeg_ctt->nb_filtergraphs; i++) {
        ret = reap_filters(pffmpeg_ctt->filtergraphs[i], 0,pffmpeg_ctt);
        if (ret < 0)
            return ret;
    }

    return 0;
}

/*
 * The following code is the main loop of the file converter
 */
static int transcode(int *err_rate_exceeded,FFMPEGContext *pffmpeg_ctt)
{
    int ret = 0, i;
    InputStream *ist;
    int64_t timer_start;

    print_stream_maps(pffmpeg_ctt);

    *err_rate_exceeded = 0;
    atomic_store(&pffmpeg_ctt->transcode_init_done, 1);

    timer_start = av_gettime_relative();

    while (!pffmpeg_ctt->received_stop) {
		if(NULL!=pffmpeg_ctt->controlCmd
			&&0==pffmpeg_ctt->controlCmd(pffmpeg_ctt->controlCmd_arg)){
			break;
		}  
        OutputStream *ost;
        int64_t cur_time= av_gettime_relative();
        ret = choose_output(&ost,pffmpeg_ctt);
        if (ret == AVERROR(EAGAIN)) {
            reset_eagain(pffmpeg_ctt);
            av_usleep(10000);
            ret = 0;
            continue;
        } else if (ret < 0) {
            av_log(NULL, AV_LOG_VERBOSE, "No more output streams to write to, finishing.\n");
            ret = 0;
            break;
        }

        ret = transcode_step(ost,pffmpeg_ctt);
        if (ret < 0 && ret != AVERROR_EOF) {
            av_log(NULL, AV_LOG_ERROR, "Error while filtering: %s\n", av_err2str(ret));
            break;
        }

        /* dump report by using the output first video and audio streams */
        //print_report(0, timer_start, cur_time,pffmpeg_ctt);
    }

    /* at the end of stream, we must flush the decoder buffers */
    for (ist = ist_iter(NULL,pffmpeg_ctt); ist; ist = ist_iter(ist,pffmpeg_ctt)) {
        float err_rate;

        if (!pffmpeg_ctt->input_files[ist->file_index]->eof_reached) {
            int err = process_input_packet(ist, NULL, 0,pffmpeg_ctt);
            ret = err_merge(ret, err);
        }

        err_rate = (ist->frames_decoded || ist->decode_errors) ?
                   ist->decode_errors / (ist->frames_decoded + ist->decode_errors) : 0.f;
        if (err_rate > max_error_rate) {
            av_log(ist, AV_LOG_FATAL, "Decode error rate %g exceeds maximum %g\n",
                   err_rate, max_error_rate);
            *err_rate_exceeded = 1;
        } else if (err_rate)
            av_log(ist, AV_LOG_VERBOSE, "Decode error rate %g\n", err_rate);
    }
    ret = err_merge(ret, enc_flush(pffmpeg_ctt));


    /* write the trailer if needed */
    for (i = 0; i < pffmpeg_ctt->nb_output_files; i++) {
        int err = of_write_trailer(pffmpeg_ctt->output_files[i],pffmpeg_ctt);
        ret = err_merge(ret, err);
    }

    /* dump report by using the first video and audio streams */
    //print_report(1, timer_start, av_gettime_relative(),pffmpeg_ctt);

    return ret;
}

static BenchmarkTimeStamps get_benchmark_time_stamps(void)
{
    BenchmarkTimeStamps time_stamps = { av_gettime_relative() };
#if HAVE_GETRUSAGE
    struct rusage rusage;

    getrusage(RUSAGE_SELF, &rusage);
    time_stamps.user_usec =
        (rusage.ru_utime.tv_sec * 1000000LL) + rusage.ru_utime.tv_usec;
    time_stamps.sys_usec =
        (rusage.ru_stime.tv_sec * 1000000LL) + rusage.ru_stime.tv_usec;
#elif HAVE_GETPROCESSTIMES
    HANDLE proc;
    FILETIME c, e, k, u;
    proc = GetCurrentProcess();
    GetProcessTimes(proc, &c, &e, &k, &u);
    time_stamps.user_usec =
        ((int64_t)u.dwHighDateTime << 32 | u.dwLowDateTime) / 10;
    time_stamps.sys_usec =
        ((int64_t)k.dwHighDateTime << 32 | k.dwLowDateTime) / 10;
#else
    time_stamps.user_usec = time_stamps.sys_usec = 0;
#endif
    return time_stamps;
}

static int64_t getmaxrss(void)
{
#if HAVE_GETRUSAGE && HAVE_STRUCT_RUSAGE_RU_MAXRSS
    struct rusage rusage;
    getrusage(RUSAGE_SELF, &rusage);
    return (int64_t)rusage.ru_maxrss * 1024;
#elif HAVE_GETPROCESSMEMORYINFO
    HANDLE proc;
    PROCESS_MEMORY_COUNTERS memcounters;
    proc = GetCurrentProcess();
    memcounters.cb = sizeof(memcounters);
    GetProcessMemoryInfo(proc, &memcounters, sizeof(memcounters));
    return memcounters.PeakPagefileUsage;
#else
    return 0;
#endif
}
FFMPEGContext *msffmepg_api_init(FFMPEGContext *pffmpeg_ctt_in,
	char (*control_cbfunc)(void *control_cbfunc_arg),void *control_cbfunc_arg,
	char (*bitrate_cbfunc)(void *bitrate_cbfunc_arg,int size,char flag_recv),void *bitrate_cbfunc_arg,
	int (*detect_cbfunc)(void * detect_cbfunc_arg,AVFrame *filt_frame,AVFrame *frame_dec,STREAMCODECContext *pscodec_ctt),void * detect_cbfunc_arg)
{
	FFMPEGContext *pffmpeg_ctt=NULL;
	void *pcontrol_cbfunc=control_cbfunc;
	void *pcontrol_cbfunc_arg=control_cbfunc_arg;
	void *pbitrate_cbfunc=bitrate_cbfunc;
	void *pbitrate_cbfunc_arg=bitrate_cbfunc_arg;
	void *pdetect_cbfunc=detect_cbfunc;
	void *pdetect_cbfunc_arg=detect_cbfunc_arg;
	if(NULL==pffmpeg_ctt_in){
		pffmpeg_ctt=av_mallocz(sizeof(FFMPEGContext));
		if(NULL==pffmpeg_ctt){
			return NULL;
		}
	}else{
		pffmpeg_ctt=pffmpeg_ctt_in;
		pcontrol_cbfunc=pffmpeg_ctt_in->controlCmd;
		pcontrol_cbfunc_arg=pffmpeg_ctt_in->controlCmd_arg;
		pbitrate_cbfunc=pffmpeg_ctt_in->bitRate;
		pbitrate_cbfunc_arg=pffmpeg_ctt_in->bitRate_arg;
		pdetect_cbfunc=pffmpeg_ctt_in->detectCodec;
		pdetect_cbfunc_arg=pffmpeg_ctt_in->detectCodec_arg;
		memset(pffmpeg_ctt, 0, sizeof(FFMPEGContext));
	}
	pffmpeg_ctt->cmdutils_ctt.pffmpeg_ctt=pffmpeg_ctt;
//ffmpeg_mux.c
	pffmpeg_ctt->want_sdp = 1;
//ffmpeg_opt.c
	pffmpeg_ctt->video_sync_method = VSYNC_AUTO;
	pffmpeg_ctt->abort_on_flags	  = 0;
	pffmpeg_ctt->stats_period = 500000;

//ffmpeg.c	
	pffmpeg_ctt->nb_output_dumped = 0;
	pffmpeg_ctt->progress_avio = NULL;
	pffmpeg_ctt->input_files	= NULL;
	pffmpeg_ctt->nb_input_files	= 0;
	pffmpeg_ctt->output_files   = NULL;
	pffmpeg_ctt->nb_output_files = 0;
	pffmpeg_ctt->transcode_init_done = ATOMIC_VAR_INIT(0);
	pffmpeg_ctt->ffmpeg_exited = 0;
	pffmpeg_ctt->copy_ts_first_pts = AV_NOPTS_VALUE;


	pffmpeg_ctt->int_cb.callback=decode_interrupt_cb;
	pffmpeg_ctt->int_cb.opaque=pffmpeg_ctt;

//cmd
	pffmpeg_ctt->controlCmd= pcontrol_cbfunc;                 
	pffmpeg_ctt->controlCmd_arg= pcontrol_cbfunc_arg;       
	pffmpeg_ctt->bitRate= pbitrate_cbfunc;                 
	pffmpeg_ctt->bitRate_arg= pbitrate_cbfunc_arg;      
	pffmpeg_ctt->detectCodec= pdetect_cbfunc;         
	pffmpeg_ctt->detectCodec_arg= pdetect_cbfunc_arg;      
	pffmpeg_ctt->runstate=0;

	return pffmpeg_ctt;
}
void msffmepg_api_deinit(FFMPEGContext **ppffmpeg_ctt)
{
	FFMPEGContext *pffmpeg_ctt=(*ppffmpeg_ctt);
	av_freep(pffmpeg_ctt);
	(*ppffmpeg_ctt)=NULL;
	
}
int msffmepg_api_split(char*  str_token,char*  str_delimit,char*  str_out[])
{
	if(NULL==str_token
		||NULL==str_delimit){
		return 0;
	}
	if(0==strlen(str_token)){
		return 0;
	}
	int strnum=0;
	char* p= strtok(str_token,str_delimit);
	
	if(p) {str_out[strnum]=p;}
	while(p){
		p=strtok(NULL,str_delimit);
		if(p){
			strnum++;
			str_out[strnum]=p;
		}
	}
	strnum+= 1;
	return strnum;
}

static int msffmepg_api_splitcmd(char*  str_token,char*  str_delimit,char*  str_out[])
{
	char* argv[1024];
	int argc=msffmepg_api_split(str_token, "\"", argv);
	int index=0;
	int argc_out=0;
	//av_log(NULL, AV_LOG_WARNING, "argc=%d\n", argc);
	for(index=0;index<argc;index++){
		//av_log(NULL, AV_LOG_WARNING, "---%s\n", argv[index]);
		if(0!=(index%2)){
			str_out[argc_out]=argv[index];
			argc_out+=1;
		}else{
			argc_out+=msffmepg_api_split(argv[index], " ", &str_out[argc_out]);
		}
	}
	//av_log(NULL, AV_LOG_WARNING, "argc_out=%d\n", argc_out);
	//for(index=0;index<argc_out;index++){
		//av_log(NULL, AV_LOG_WARNING, "11---%s\n", str_out[index]);
	//}
	return argc_out;
}
static char msffmpeg_api_counter_async(time_t *  Etimep,unsigned int  sec)	//s
{
	time_t timep=time(NULL);
	if(timep>((*Etimep)+sec)){
		(*Etimep)=timep;
		return 1;
	}else{
		return 0;
	}
} 
void msffmepg_api_envinit()
{
	init_dynload();
	setvbuf(stderr,NULL,_IONBF,0); /* win32 runtime needs this */
	av_log_set_flags(AV_LOG_SKIP_REPEATED);
#if CONFIG_AVDEVICE
	avdevice_register_all();
#endif
	avformat_network_init();
}
void msffmepg_api_envdeinit()
{
	avformat_network_deinit();
}
int msffmepg_api_runcmd(int argc,char** argv,FFMPEGContext *pffmpeg_ctt)
{
	if(1==pffmpeg_ctt->runstate){
		av_log(NULL, AV_LOG_FATAL, "already run,state:%d\n",pffmpeg_ctt->runstate);
		return -1;
	}
	pffmpeg_ctt->runstate=1;

 	int ret, err_rate_exceeded;
    BenchmarkTimeStamps ti;

    init_dynload();

    setvbuf(stderr,NULL,_IONBF,0); /* win32 runtime needs this */

    av_log_set_flags(AV_LOG_SKIP_REPEATED);
    parse_loglevel(argc, argv, options,&pffmpeg_ctt->cmdutils_ctt);

#if CONFIG_AVDEVICE
    avdevice_register_all();
#endif
    avformat_network_init();

    show_banner(argc, argv, options,&pffmpeg_ctt->cmdutils_ctt);

    /* parse options and open all input/output files */
    ret = ffmpeg_parse_options(argc, argv,&pffmpeg_ctt->cmdutils_ctt);
    if (ret < 0){
		av_log(NULL, AV_LOG_ERROR, "ffmpeg_parse_options failed\n");
		pffmpeg_ctt->main_return_code=-1;
        goto finish;
    }
	av_log(NULL, AV_LOG_INFO, "nb_output_files,nb_input_files:%d,%d\n",pffmpeg_ctt->nb_output_files,pffmpeg_ctt->nb_input_files );
    if (pffmpeg_ctt->nb_output_files <= 0 && pffmpeg_ctt->nb_input_files == 0) {
        show_usage();
        av_log(NULL, AV_LOG_WARNING, "Use -h to get full help or, even better, run 'man %s'\n", program_name);
        ret = 1;
		pffmpeg_ctt->main_return_code=-1;
        goto finish;
    }

    if (pffmpeg_ctt->nb_output_files <= 0) {
        av_log(NULL, AV_LOG_FATAL, "At least one output file must be specified\n");
        ret = 1;
		pffmpeg_ctt->main_return_code=-1;
        goto finish;
    }

    pffmpeg_ctt->current_time = ti = get_benchmark_time_stamps();
    ret = transcode(&err_rate_exceeded,pffmpeg_ctt);
    if (ret >= 0 && do_benchmark) {
        int64_t utime, stime, rtime;
        pffmpeg_ctt->current_time = get_benchmark_time_stamps();
        utime = pffmpeg_ctt->current_time.user_usec - ti.user_usec;
        stime = pffmpeg_ctt->current_time.sys_usec  - ti.sys_usec;
        rtime = pffmpeg_ctt->current_time.real_usec - ti.real_usec;
        av_log(NULL, AV_LOG_INFO,
               "bench: utime=%0.3fs stime=%0.3fs rtime=%0.3fs\n",
               utime / 1000000.0, stime / 1000000.0, rtime / 1000000.0);
    }

    ret = err_rate_exceeded ? 69 : ret;

finish:
	ffmpeg_cleanup(ret,pffmpeg_ctt);
	pffmpeg_ctt->received_stop=0;
	pffmpeg_ctt->runstate=2;
	return pffmpeg_ctt->main_return_code;
}


int msffmepg_api_run(char * cmdline,FFMPEGContext *pffmpeg_ctt)
{
	char* argv[1024];
	int argc=msffmepg_api_splitcmd(cmdline, " ", argv);
	return msffmepg_api_runcmd(argc,argv,pffmpeg_ctt);
}

char msffmepg_api_taskstop(FFMPEGContext *pffmpeg_ctt,char flag_waitstop)
{
	time_t sys_base=time((time_t*)NULL);
	if(NULL!=pffmpeg_ctt){
		av_log(NULL, AV_LOG_INFO, "send stop to ffmpeg,runstate:%d\n",pffmpeg_ctt->runstate);
		pffmpeg_ctt->received_stop=1;
		if(1==flag_waitstop){
			while((1==pffmpeg_ctt->received_stop)&&(2!=pffmpeg_ctt->runstate)){
				if(1==msffmpeg_api_counter_async((time_t * )&sys_base, 60)){
					if(1==pffmpeg_ctt->runstate){
						av_log(NULL, AV_LOG_FATAL, "msffmpeg_api_counter_async timeout(30 s),runstate:%d\n",pffmpeg_ctt->runstate);
						return -1;
					}else{
						return 0;
					}
				}
				//100ms
				usleep(100000);
			}
		}
		return 0;
	}else{
		av_log(NULL, AV_LOG_ERROR, "pffmpeg_ctt is null,send stop to ffmpeg failed\r\n");
	}
	return 1;
}

char * msffmepg_api_version()
{
	return "01.00.12";
}

